Another post in what is slowly becoming a series, after describing how to make a Discord bot with PHP; today we're looking at how to make a Discord activity the same way.
An activity is simpler than a bot; Discord activities are basically a web page which loads in an iframe, and can do what it likes in there. You're supposed to use them for games and the like, but I suspect that it might be useful to do quite a few bot-like tasks with activities instead; they take up more of your screen while you're using them, but it's much, much easier to create a user-friendly experience with an activity than it is with a bot. The user interface for bots tends to look a lot like the command line, which appeals to nerds, but having to type !mybot -opt 1 -opt 2
is incomprehensible gibberish to real people. Build a little web UI, you know it makes sense.
Anyway, I have not yet actually published one of these activities, and I suspect that there is a whole bunch of complexity around that which I'm not going to get into yet. So this will get you up and running with a Discord activity that you can test, yourself. Making it available to others is step 2: keep an eye out for a post on that.
There are lots of "frameworks" out there for building Discord activities, most of which are all about "use React!" and "have this complicated build environment!" and "deploy a node.js server!", when all you actually need is an SPA web page1, a JS library, a small PHP file, and that's it. No build step required, no deploying a node.js server, just host it in any web space that does PHP (i.e., all of them). Keep it simple, folks. Much nicer.
Step 1: set up a Discord app
To have an activity, it's gotta be tied to a Discord app. Get one of these as follows:
- Create an application at discord.com/developers/applications. Call it whatever you want
- Copy the "Application ID" from "General Information" and make a
secrets.php
file; add the application ID as$clientid = "whatever";
- In "OAuth2", "Reset Secret" under Client Secret and store it in
secrets.php
as $clientsecret - In "OAuth2", "Add Redirect": this URL doesn't get used but there has to be one, so fill it in as some URL you like (
http://127.0.0.1
works fine) - Get the URL of your activity web app (let's say it's
https://myserver/myapp/
). Under URL Mappings, addmyserver/myapp
(nohttps://
) as the Root Mapping. This tells Discord where your activity is - Under Settings, tick Enable Activities. (Also tick "iOS" and "Android" if you want it to work in the phone app)
- Under Installation > Install Link, copy the Discord Provided Link. Open it in a browser. This will switch to the Discord desktop app. Add this app to the server of your choice (not to everywhere), and choose the server you want to add it to
- In the Discord desktop client, click the Activities button (it looks like a playstation controller, at the end of the message entry textbox). Your app should now be in "Apps in this Server". Choose it and say Launch. Confirm that you're happy to trust it because you're running it for the first time
And this will then launch your activity in a window in your Discord app. It won't do anything yet because you haven't written it, but it's now loading.
Step 2: write an activity
- You'll need the Discord Embedded SDK JS library. Go off to jsdelivr and see the URL it wants you to use (at time of writing this is
https://cdn.jsdelivr.net/npm/@discord/embedded-app-sdk@2.0.0/+esm
but check). Download this URL to get a JS file, which you should call discordsdk.js. (Note: do not link to this directly. Discord activities can't download external resources without some semi-complex setup. Just download the JS file) - Now write the home page for your app -- index.php is likely to be ideal for this, because you need the client ID that you put in
secrets.php
. A very basic one, which works out who the user is, looks something like this:
<html>
<body>
I am an activity! You are <output id="username">...?</output>
<scr ipt type="module">
import {DiscordSDK} from './discordsdk.js';
const clientid = '<?php echo $clientid; ?>';
async function setup() {
const discordSdk = new DiscordSDK(clientid);
// Wait for READY payload from the discord client
await discordSdk.ready();
// Pop open the OAuth permission modal and request for access to scopes listed in scope array below
const {code} = await discordSdk.commands.authorize({
client_id: clientid,
response_type: 'code',
state: '',
prompt: 'none',
scope: ['identify'],
});
const response = await fetch('/.proxy/token.php?code=' + code);
const {access_token} = await response.json();
const auth = await discordSdk.commands.authenticate({access_token});
document.getElementById("username").textContent = auth.user.username;
/* other properties you may find useful:
server ID: discordSdk.guildId
user ID: auth.user.id
channel ID: discordSdk.channelId */
}
setup()
You will see that in the middle of this, we call token.php
to get an access token from the code
that discordSdk.commands.authorize
gives you. While the URL is /.proxy/token.php
, that's just a token.php
file right next to index.php
; the .proxy
stuff is because Discord puts all your requests through their proxy, which is OK. So you need this file to exist. Following the Discord instructions for authenticating users with OAuth, it should look something like this:
<?php
require_once("secrets.php");
$postdata = http_build_query(
array(
"client_id" => $clientid,
"client_secret" => $clientsecret,
"grant_type" => "authorization_code",
"code" => $_GET["code"]
)
);
$opts = array('http' =>
array(
'method' => 'POST',
'header' => [
'Content-Type: application/x-www-form-urlencoded',
'User-Agent: mybot/1.00'
],
'content' => $postdata,
'ignore_errors' => true
)
);
$context = stream_context_create($opts);
$result_json = file_get_contents('https://discord.com/api/oauth2/token', false, $context);
if ($result_json == FALSE) {
echo json_encode(array("error"=>"no response"));
die();
}
$result = json_decode($result_json, true);
if (!array_key_exists("access_token", $result)) {
error_log("Got JSON response from /token without access_token $result_json");
echo json_encode(array("error"=>"no token"));
die();
}
$access_token = $result["access_token"];
echo json_encode(array("access_token" => $access_token));
And... that's all. At this point, if you Launch your activity from Discord, it should load, and should work out who the running user is (and which channel and server they're in) and that's pretty much all you need. Hopefully that's a relatively simple way to get started.
- it's gotta be an SPA. Discord does not like it when the page navigates around ↩